//
//  GameScene.m
//  CandyJump
//
//  Created by Bon Lam on 26/02/2011.
//  Copyright 2011 Peru Republic. All rights reserved.
//

#import "GameScene.h"

#import "Character.h"
#import	"Platform.h"
#import "Model.h"

#import "GameDataManager.h"
#import "PhysicsManager.h"
#import	"SkillManager.h"

#import "BackgroundRes.h"
#import "ObjectRes.h"
#import "CharBuffRes.h"
#import "SkillRes.h"
#import "SpriteSheetRes.h"

#import "GCpShapeCache.h"

//---------------------------------------------------------------------------------------------------------------------
#define GAMESCENE_PHYSICS_DATA_FILE							@"StaticData/Objects/PhysicsData.plist"

#define GAMESCENE_GAMEPLAY_CHARACTER_MAX_HEIGHT_RATIO		0.5			//	1 = whole screen height, 0.5 = half...etc.

#pragma mark -
//=====================================================================================================================
@implementation GameScene

static GameScene* gameSceneInstance;

#pragma mark Object Functions
-(id) init
{
	if ((self = [super init]))
	{
		NSAssert(gameSceneInstance == nil, @"another MultiLayerScene is already in use!");
		gameSceneInstance = self;
		
		// The GameLayer will be moved, rotated and scaled independently from other layers.
		// It also contains a number of moving pseudo game objects to show the effect on child nodes of GameLayer.
		GameLayer* gameLayer = [GameLayer node];
		[self addChild:gameLayer z:1 tag:0];
		
		// The UserInterfaceLayer remains static and relative to the screen area.
		GameUILayer* UILayer = [GameUILayer node];
		[self addChild:UILayer z:2 tag:1];
	}
	
	return self;
}

-(void) dealloc
{
	CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);
	
	// The Layer will be gone now, to avoid crashes on further access it needs to be nil.
	gameSceneInstance = nil;
	
	// don't forget to call "super dealloc"
	[super dealloc];
}

#pragma mark Singleton Functions
+(GameScene*) sharedLayer
{
	NSAssert(gameSceneInstance != nil, @"Game Scene not available!");
	return gameSceneInstance;
}

-(GameLayer*) gameLayer
{
	CCNode* layer = [[GameScene sharedLayer] getChildByTag:0];
	NSAssert([layer isKindOfClass:[GameLayer class]], @"%@: not a GameLayer!", NSStringFromSelector(_cmd));
	return (GameLayer*)layer;
}

-(GameUILayer*) UILayer
{
	CCNode* layer = [[GameScene sharedLayer] getChildByTag:1];
	NSAssert([layer isKindOfClass:[GameUILayer class]], @"%@: not a GameUILayer!", NSStringFromSelector(_cmd));
	return (GameUILayer*)layer;
}

+ (id)scene
{	
	CCScene *scene = [CCScene node];
	GameScene* layer = [GameScene node];
	[scene addChild:layer];
	return scene;
}

@end

#pragma mark -
//=====================================================================================================================
@implementation GameUILayer

#pragma mark Object Functions
//---------------------------------------------------------------------------------------------------------------------
- (id)init
{
	if ((self = [super init]))
	{
		//	CCLayer setup
		self.isTouchEnabled = YES;
		self.isAccelerometerEnabled = NO;
		
		// Ugly hack to the CoolDown
		skillButtonOne = [CCProgressTimer progressWithFile:@"skill_bubble.png"];
		skillButtonOne.type = kCCProgressTimerTypeRadialCW;
		[skillButtonOne setPosition:ccp(274, 119.2)];
		[skillButtonOne setScale:0.7f];
		skillButtonOne.percentage = 100;
		[self addChild:skillButtonOne z:1 tag:0];
		skillOne = 6; skillOneCD = NO; skillOneCDTime = 15;
		
		skillButtonTwo = [CCProgressTimer progressWithFile:@"skill_jump.png"];
		skillButtonTwo.type = kCCProgressTimerTypeRadialCW;
		[skillButtonTwo setPosition:ccp(274, 36.4)];
		[skillButtonTwo setScale:0.7f];
		skillButtonTwo.percentage = 100;
		[self addChild:skillButtonTwo z:1 tag:1];
		skillTwo = 2; skillTwoCD = NO; skillTwoCDTime = 15;
	}
	
	return self;
}

//---------------------------------------------------------------------------------------------------------------------
- (void)dealloc
{
	[super dealloc];
}

#pragma mark CCStandardTouchDelegate
//---------------------------------------------------------------------------------------------------------------------
-(void) registerWithTouchDispatcher
{
	[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}

-(BOOL) isTouchForButtonOne:(CGPoint)touchLocation
{
	CCNode* node = [self getChildByTag:0];
	return CGRectContainsPoint([node boundingBox], touchLocation);
}

-(BOOL) isTouchForButtonTwo:(CGPoint)touchLocation
{
	CCNode* node = [self getChildByTag:1];
	return CGRectContainsPoint([node boundingBox], touchLocation);
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
	CGPoint location = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
	BOOL isTouchHandled = [self isTouchForButtonOne:location] || [self isTouchForButtonTwo:location];
	if (isTouchHandled)
	{
		// Cast the skill
		GameLayer* gameLayer = [[GameScene sharedLayer] gameLayer];
		if ([self isTouchForButtonOne:location] && skillOneCD == NO) {
			skillOneCD = YES;
			[gameLayer castSkill:skillOne];
			[self schedule:@selector(resetSkillOne:) interval:skillOneCDTime];
			[skillButtonOne runAction:[CCProgressFromTo actionWithDuration:skillOneCDTime from:0 to:100]];
		}
		if ([self isTouchForButtonTwo:location] && skillTwoCD == NO) {
			skillTwoCD = YES;
			[gameLayer castSkill:skillTwo];
			[self schedule:@selector(resetSkillTwo:) interval:skillTwoCDTime];
			[skillButtonTwo runAction:[CCProgressFromTo actionWithDuration:skillTwoCDTime from:0 to:100]];
		}
	}
	
	return isTouchHandled;
}

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
	
}

-(void) resetSkillOne:(ccTime)delta
{
	[self unschedule:_cmd];
	skillOneCD = NO;
	skillButtonOne.percentage = 100;
}

-(void) resetSkillTwo:(ccTime)delta
{
	[self unschedule:_cmd];
	skillTwoCD = NO;
	skillButtonTwo.percentage = 100;
}

@end

#pragma mark -
//=====================================================================================================================
@interface GameLayer ()

// Background related
//---------------------------------------------------------------------------------------------------------------------
- (void)addBackgroundObjects;
- (void)removeBackgroundObjects;
@end

#pragma mark -
//=====================================================================================================================
@implementation GameLayer


#pragma mark Object Functions
//---------------------------------------------------------------------------------------------------------------------
- (id)init
{
	self = [super init];
	if (self == nil) {return self;}
	
	//	CCLayer setup
	self.isTouchEnabled = YES;
	self.isAccelerometerEnabled = YES;

	
	//********	Setup physics	********
	CGSize winSize = [CCDirector sharedDirector].winSize;
	cpInitChipmunk();
	space = cpSpaceNew();
	
	cpBody *staticBody = cpBodyNew(INFINITY, INFINITY);
	space = cpSpaceNew();
//	cpSpaceResizeStaticHash(space, 400.0f, 40);
//	cpSpaceResizeActiveHash(space, 100, 600);
	
	space->gravity = ccp(0, CANDY_JUMP_GLOBAL_PHYSICS_GRAVITY);
	space->elasticIterations = space->iterations = 10;
	//space->elasticIterations = 10;									// Add some elastic effects to the simulation
	cp_collision_slop = 1.0;
	cp_bias_coef = 0.3;
	
	cpShape *shape;
	
	// bottom
	shape = cpSegmentShapeNew(staticBody, ccp(0,0), ccp(winSize.width,0), 0.0f);
	shape->e = 1.0f; shape->u = 1.0f;
	cpSpaceAddStaticShape(space, shape);
	
	// top
//	shape = cpSegmentShapeNew(staticBody, ccp(0,winSize.height), ccp(winSize.width,winSize.height), 0.0f);
//	shape->e = 1.0f; shape->u = 1.0f;
//	cpSpaceAddStaticShape(space, shape);
	
	// left
	shape = cpSegmentShapeNew(staticBody, ccp(0,0), ccp(0,winSize.height), 0.0f);
	shape->e = 1.0f; shape->u = 1.0f;
	cpSpaceAddStaticShape(space, shape);
	
	// right
	shape = cpSegmentShapeNew(staticBody, ccp(winSize.width,0), ccp(winSize.width,winSize.height), 0.0f);
	shape->e = 1.0f; shape->u = 1.0f;
	cpSpaceAddStaticShape(space, shape);

	
	//********	Init instance varibles	********
	//	Init managers
	stageManager = [StageManager sharedManager];
	[stageManager setDelegate:self];
	
	physicsManager = [PhysicsManager sharedManager];
	[PhysicsManager setupCollisionCallbacks:space];
	
	skillManager = [SkillManager sharedManager];
	
	//******** Load Static Data ********
	//	Load textures and spritesheets, add to scene
	SpriteSheetRes *spriteSheetRes = [SpriteSheetRes sharedRes];
	[spriteSheetRes loadSpriteSheets];
	
	//	Sort and add to layer
	NSMutableDictionary *batchNodeDictionary = [spriteSheetRes batchNodeDictionary];
	NSMutableArray *batchNodeArray = [[NSMutableArray alloc] init];
	for (NSString* key in [batchNodeDictionary allKeys]) { 
		[batchNodeArray addObject:[batchNodeDictionary objectForKey:key]];
	}
	
	NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"vertexZ" ascending:YES] autorelease];
	NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
	NSArray *sortedbatchNodeArray = [batchNodeArray sortedArrayUsingDescriptors:sortDescriptors];
	for (CCSpriteBatchNode* batchnode in sortedbatchNodeArray) {
		[self addChild:batchnode];
	}
	
	[batchNodeArray release]; batchNodeArray = nil;
	
	
	
	//	Load physics data and set up physics space
	[[GCpShapeCache sharedShapeCache] addShapesWithFile:GAMESCENE_PHYSICS_DATA_FILE];
	//	Load object data
	[[ObjectRes sharedRes] loadObjects];
	
	
	//********	Load Background	********
	[stageManager loadBackgroundWithName:@"factory"];
	
	//********	Temp try to add model	********
	ObjectRes *objectRes = [ObjectRes sharedRes];
	for (int i=0; i<10; i++) {

		float posx = (CCRANDOM_0_1() * winSize.width);
		float posy = (CCRANDOM_0_1() * winSize.height);
		
		Model *model = [[[objectRes objectListDictionary] objectForKey:@"platform_01_standard"] copy];
		model.sprite.position = ccp(posx, posy);
		
		CCSpriteBatchNode *batchnode = [batchNodeDictionary objectForKey:model.batchnodeName];
		[batchnode addChild:model.sprite];
		
		[model setUpPhysics:space];

		[[stageManager modelArray] addObject:model];
		[model release];
	}
	
	//********	Temp create character	********
	Model *characterModel = [[[objectRes objectListDictionary] objectForKey:@"character_01_candy"] copy];
	characterModel.sprite.position = ccp(winSize.width/2, winSize.height/2);

	CCSpriteBatchNode *batchnode = [batchNodeDictionary objectForKey:characterModel.batchnodeName];
	//[characterModel playAnimation:@"animIdle"];
	
	[batchnode addChild:characterModel.sprite];
	
	[characterModel setUpPhysics:space];
	
	[[stageManager modelArray] addObject:characterModel];
	
	character = (Character*)characterModel;
	[characterModel release];
	
	//********	Add schedule	********
	[self schedule: @selector(step:)];
	
	return self;
}

//---------------------------------------------------------------------------------------------------------------------
- (void)dealloc
{
	[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
	
	[super dealloc];
}

#pragma mark Custom Functions
//---------------------------------------------------------------------------------------------------------------------
- (void)step:(ccTime)dt
{
	//	Process physics
	cpSpaceStep(space, dt);
	
	//	Update camera if character at the middle
	CGSize screenSize = [CCDirector sharedDirector].winSize;
	CGFloat screenHeight = screenSize.height;
	CGFloat screenWidth = screenSize.width;
	cpBody* characterBody = [character physicsBody];
	BOOL scrolled = NO;
	
	if (characterBody->p.y > currentheight + screenHeight*GAMESCENE_GAMEPLAY_CHARACTER_MAX_HEIGHT_RATIO) {
		currentheight = (int)(characterBody->p.y - screenHeight*GAMESCENE_GAMEPLAY_CHARACTER_MAX_HEIGHT_RATIO);
		scrolled = YES;
	}
	
	//	Make the character always on the screen
	if (characterBody->p.x > screenWidth) {
		characterBody->p.x = 0;
	}
	
	if (characterBody->p.x < 0) {
		characterBody->p.x = screenWidth;
	}

	//	Fetch and update the objects
	for (Model* model in [stageManager modelArray]) {
		
		if (scrolled) {
			[model setRenderOffset:CGPointMake(0, -currentheight)];
		}
		
		[model update:dt];
	}
	
	[stageManager manageObjects];
	[stageManager manageBackground];
	//NSLog(@"%d", currentheight);
}

//---------------------------------------------------------------------------------------------------------------------
- (void)resetGame
{
	
}

#pragma mark Skill related function
//---------------------------------------------------------------------------------------------------------------------
// Testing skill cast function
// Currently ugly and hard-coded for testing
- (void)castSkill:(int) skill
{
	//CharBuffRes* charBuffRes = [CharBuffRes sharedRes];
	//[charBuffRes loadCharBuff];
	//CharacterBuffInfo* buffInfo = [[charBuffRes characterBuffInfoDict] objectForKey:@"JUMP_BOOST_01"];
		
	//SkillRes* skillRes = [SkillRes sharedRes];
	//[skillRes loadSkills];
	//Skill* POWER_JUMP = [[skillRes skillListDictionary] objectForKey:@"SKILL_POWER_JUMP_01"];
	
	cpBody* characterBody = [character physicsBody];
	ObjectRes* objectRes;
	Model* model;
	CCSprite* mud;
	
	switch (skill) {
		case 1:
			// Jump - Simply apply an impulse to the body
			cpBodySetVelLimit(characterBody, 1200.0f);
			cpBodyResetForces(characterBody);
			cpBodyApplyImpulse(characterBody, cpv(0, 1500), cpvzero);
			break;
		case 2:
			// Rocket - Apply a small impluse and force to the body
			cpBodySetVelLimit(characterBody, 1200.0f);
			cpBodyResetForces(characterBody);
			cpBodyApplyImpulse(characterBody, cpv(0, 1200), cpvzero);	
			cpBodyApplyForce(characterBody, cpv(0, 1200), cpvzero);
			[self schedule:@selector(resetForce:) interval:5.0f];
			break;
		case 3:
			// Bubble - Fly up with constant speed
			cpBodySetVelLimit(characterBody, 350.0f);
			cpBodyResetForces(characterBody);
			cpBodyApplyImpulse(characterBody, cpv(0, 700), cpvzero);
			cpBodyApplyForce(characterBody, cpv(0, 700), cpvzero);
			[self schedule:@selector(resetForce:) interval:10.0f];
			break;
		case 4:
			// Platform builder - Instantly build a platform under the character
			objectRes = [ObjectRes sharedRes];
			model = [[[objectRes objectListDictionary] objectForKey:@"platform_01_standard"] copy];
			model.sprite.position = ccp(characterBody->p.x, characterBody->p.y - 50);
			[model setRenderOffset:CGPointMake(0, -currentheight)];
			
			[[stageManager modelArray] addObject:model];
			
			[self addStageObject:model];
			
			[model release];
			break;
		case 5:
			// Mud - Paste an image on the screen and then gradually fade out
			// Need some more effect when appear?
			mud = [CCSprite spriteWithFile:@"Icon@2x.png"];
			mud.position = ccp(160, 240);
			[self addChild:mud z:99 tag:99];
			[self schedule:@selector(fadeOutMud:) interval:6.0f];
			break;
		case 6:
			// Magnet - Attract the stars and/or items towards the character
			// TODO: Need to ignore the collision between stars-stars and items-items (model of same type)
			// TODO: And handle the collision between items and stars with character
			[self schedule:@selector(startMagnet:)];
			[self schedule:@selector(stopMagnet:) interval:5.0f];
			break;
		case 7:
			// Teleport
			break;
		default:
			NSLog(@"Wrong skill to cast");
			break;
	}
}

-(void) startMagnet:(ccTime)delta
{
	cpBody* characterBody = [character physicsBody];
	
	for (Model *model in [stageManager modelArray]) {
		// Use platform to test
		if ([model isKindOfClass:[Platform class]] == YES) {
			Platform* platform = (Platform*) model;
			cpBody* platformBody = [platform physicsBody];
			// Should set a schedule and move all bodies towards the character
			// P.S. Larger the float, the items / stars will move faster towards character
			cpBodyUpdateVelocity(platformBody, cpv(characterBody->p.x - platformBody->p.x, characterBody->p.y - platformBody->p.y), 2.0f, delta);
			cpBodyUpdatePosition(platformBody, delta);
		}
	}
}

-(void) stopMagnet:(ccTime)delta
{
	[self unschedule:_cmd];
	[self unschedule:@selector(startMagnet:)];
}
			 
-(void) resetForce:(ccTime)delta
{
	[self unschedule:_cmd];
	cpBody* characterBody = [character physicsBody];
	cpBodyResetForces(characterBody);
	cpBodySetVelLimit(characterBody, 9999.0f);
}

-(void) fadeOutMud:(ccTime)delta
{
	[self unschedule:_cmd];
	CCNode* node = [self getChildByTag:99];
	CCSprite* sprite = (CCSprite*) node;
	CCCallFunc* removeMud = [CCCallFunc actionWithTarget:self selector:@selector(removeMud)];
	[sprite runAction:[CCSequence actions:[CCFadeOut actionWithDuration:3.0f], removeMud, nil]];
}

-(void) removeMud
{
	[self removeChildByTag:99 cleanup:YES];
}

// Background related
//---------------------------------------------------------------------------------------------------------------------
- (void)addBackgroundObjects
{

}

//---------------------------------------------------------------------------------------------------------------------
- (void)removeBackgroundObjects
{

}


#pragma mark System Delegates
//---------------------------------------------------------------------------------------------------------------------
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
	//NSLog(@"x:%f, y:%f, z:%f", acceleration.x, acceleration.y, acceleration.z);
#if !(TARGET_IPHONE_SIMULATOR)
	cpBody* characterBody = [character physicsBody];
	characterBody->v = cpv(acceleration.x * 250, characterBody->v.y);
#endif
}

#pragma mark CCStandardTouchDelegate
//---------------------------------------------------------------------------------------------------------------------
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

}

//---------------------------------------------------------------------------------------------------------------------
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
#if TARGET_IPHONE_SIMULATOR
	UITouch *touch = [[event allTouches] anyObject];
	CGPoint location = [touch locationInView: [touch view]];
	location = [[CCDirector sharedDirector] convertToGL: location];
	
	cpBody* characterBody = [character physicsBody];
	characterBody->v = cpv(location.x - characterBody->p.x, characterBody->v.y);
#endif
}

//---------------------------------------------------------------------------------------------------------------------
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
#if TARGET_IPHONE_SIMULATOR
	cpBody* characterBody = [character physicsBody];
	characterBody->v = cpv(0, characterBody->v.y);
#endif
}

//---------------------------------------------------------------------------------------------------------------------
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{

}

#pragma mark StageManager Delegates
//---------------------------------------------------------------------------------------------------------------------
- (Character*)character
{
	return character;
}

//---------------------------------------------------------------------------------------------------------------------
- (int)currentheight
{
	return currentheight;
}

//---------------------------------------------------------------------------------------------------------------------
- (void)addStageObject:(Model*)model
{
	SpriteSheetRes *spriteSheetRes = [SpriteSheetRes sharedRes];
	NSMutableDictionary *batchNodeDictionary = [spriteSheetRes batchNodeDictionary];
	
	CCSpriteBatchNode *batchnode = [batchNodeDictionary objectForKey:model.batchnodeName];
	[batchnode addChild:model.sprite];
	
	[model setUpPhysics:space];
}

//---------------------------------------------------------------------------------------------------------------------
- (void)removeStageObject:(Model*)model
{
	
}

//---------------------------------------------------------------------------------------------------------------------
- (void)removeAllStageObjects
{
	
}

@end